package de.rts.logic;

import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Comparator;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;

import de.rts.components.RailComponent;
import de.rts.components.RailwayMap;
import de.rts.components.Train;
import de.rts.ui.RailwayLogIFrame;
import de.rts.ui.RailwayMapPanel;
import de.rts.ui.TimeLine;

public class TrainController extends MouseAdapter
{

  private RailwayMap                railwayMap;
  private RailwayMapPanel           pane;
  private RailwayLogIFrame          logiframe;
  private Set<Train>                trains;
  private Map<Integer, Train>       priorityMap;
  private boolean                   stopped;
  private Painter                   painter;
  private TrainScheduler            scheduler;
  private TimeLine                  timeLine;
  private TimeLinePointer           pointer;
  private final Comparator<Integer> comparator = new Comparator<Integer>()
                                               {

                                                 @Override
                                                 public int compare(Integer i1, Integer i2)
                                                 {
                                                   return i1.compareTo(i2);
                                                 }
                                               };

  public TrainController(RailwayMapPanel pane, RailwayMap railwayMap, RailwayLogIFrame logiframe)
  {
    this.logiframe = logiframe;
    this.pane = pane;
    this.pane.addMouseListener(this);
    this.railwayMap = railwayMap;
    this.scheduler = new TrainScheduler();
    this.logiframe.setScheduler(scheduler);
    this.trains = this.railwayMap.getTrains();
    this.timeLine = this.pane.getTimeLine();
    this.timeLine.setTrains(trains);
    this.priorityMap = new TreeMap<Integer, Train>(comparator);
    setTrainPriorities(trains);
    if (painter != null && painter.isAlive())
    {
      painter.interrupt();
      painter = null;
    }
    painter = new Painter();
    painter.start();
    Iterator<Train> iter = this.trains.iterator();
    this.pointer = new TimeLinePointer();
    while (iter.hasNext())
    {
      Train train = iter.next();
      train.setScheduler(scheduler);
      train.addPropertyChangeListener(this.pane.getListener());
      this.scheduler.notifyLog(train.toString() + "\n");
      train.start();
    }
    this.pointer.start();
  }

  protected Map<Integer, Train> getPriorityMap()
  {
    return priorityMap;
  }

  private void setTrainPriorities(Set<Train> trains)
  {
    int priority = 1;
    Train[] trainsArr = trains.toArray(new Train[0]);
    computeDeadlines(trainsArr);
    for (Train train : trainsArr)
      this.priorityMap.put(train.getDeadline(), train);
    Iterator<Entry<Integer, Train>> iter = priorityMap.entrySet().iterator();
    int i = 0;
    while (iter.hasNext())
    {
      Entry<Integer, Train> entry = iter.next();
      Train train = entry.getValue();
      train.setTrainPriority(priority++);
      trainsArr[i] = train;
      i++;
    }

    for (Train train : trainsArr)
    {
      priorityMap.remove(train.getDeadline());
      priorityMap.put(train.getTrainPriority(), train);
    }
  }

  private void computeDeadlines(Train[] trainsArr)
  {
    float routeDistance;
    float netTime, delayTime, difTime;
    float d2s, s2e;
    Train delayTrain = null, noDelayTrain = null;
    boolean cargoDelayed = false;
    int cargoDeadline = 0;

    for (Train train : trainsArr)
    {
      String trainId = train.getTrainId();
      if (!trainId.equals("2")) continue;
      {
        for (Train train2 : trainsArr)
        {
          String trainId2 = train2.getTrainId();
          if (trainId2.equals("1") || trainId2.equals("3"))
          {
            if (trainId2.equals("1"))
            {
              routeDistance = RailComponent.ROUTE1_DISTANCE;
              d2s = RailComponent.DEPARTURE_TO_S23_S56_DISTANCE;
              s2e = RailComponent.S23_S56_ST_END_DISTANCE;
            }
            else
            {
              routeDistance = RailComponent.ROUTE3_DISTANCE;
              d2s = RailComponent.DEPARTURE_TO_S1213_S1011_DISTANCE;
              s2e = RailComponent.S1213_S1011_ST_END_DISTANCE;
            }

            float dt1 = d2s / (train.getVelocity() / 10);
            float dt2 = d2s / (train2.getVelocity() / 10);
            if (dt1 > dt2)
            {
              difTime = dt1 - dt2;
              delayTrain = train;
              noDelayTrain = train2;
            }
            else
            {
              difTime = dt2 - dt1;
              delayTrain = train2;
              noDelayTrain = train;
            }
           
            netTime = (routeDistance - 105f) / (noDelayTrain.getVelocity() / 10f);
            noDelayTrain.setDeadline((int) Math.ceil(netTime));
            
            delayTime = Math.abs((s2e - (difTime * noDelayTrain.getVelocity() / 10 + d2s + 105f))
              / (noDelayTrain.getVelocity() / 10f));
            
            if (delayTrain.getTrainId().equals("2"))
              routeDistance = RailComponent.ROUTE2_DISTANCE;
            
            netTime = (routeDistance - 105f) / (delayTrain.getVelocity() / 10f);
            int delayTrainDeadline = (int) Math.ceil(netTime + delayTime);

            if (delayTrain.getTrainId().equals("2") && delayTrain.getDeadline() != 0 && cargoDelayed
              && delayTrainDeadline < cargoDeadline) continue;

            if (delayTrain.getTrainId().equals("2"))
            {
              cargoDelayed = true;
              cargoDeadline = delayTrainDeadline;
            }

            delayTrain.setDeadline(delayTrainDeadline);
          }
        }
        break;
      }
    }
  }

  class Painter extends Thread
  {

    public Painter()
    {
      super("Painter");
      setPriority(9);
      setDaemon(true);
    }

    @Override
    public void run()
    {
      while (!isInterrupted())
      {
        try
        {
          pane.repaint();
          Thread.sleep(20);
        }
        catch (InterruptedException e)
        {
          Thread.currentThread().interrupt();
        }
      }
    }
  }

  class TimeLinePointer extends Thread
  {

    private int shiftCount = 0;

    @Override
    public void run()
    {
      while (!isInterrupted())
      {
        try
        {
          if (this.shiftCount >= 915) Thread.currentThread().interrupt();
          timeLine.setTimeShiftOffset(shiftCount);
          this.shiftCount += 3;
          Thread.sleep(200);
        }
        catch (InterruptedException ignore)
        {
          interrupt();
        }
      }
    }
  }

  @Override
  public void mouseClicked(MouseEvent paramMouseEvent)
  {
    stopped = !stopped;

    if (stopped)
    {
      pointer.suspend();
      for (int i = 1; i < priorityMap.size() + 1; i++)
      {
        Train train = priorityMap.get(i);
        train.suspend();
      }
    }
    else
    {
      pointer.resume();
      for (int i = 1; i < priorityMap.size() + 1; i++)
      {
        Train train = priorityMap.get(i);
        train.resume();
      }
    }
  }
}
